/*
 * ControlApp.java
 * This file is the main controller object.
 * The controller has several responsibility corresponding to several http handler as messaging interface
 * The controller has two handler
 * HandlerRequest: handle request from client, load balance and dispatch the request to the 
 * 		appropriate agent; get socket info from the agent and relay the info to the client
 * HandleMonitor: monitor the connection start and destroy info from agent, and persist the info into database
 * 
 *  author:yanggang@kingsoft.com, wenjiesha.victor@gmail.com
 * 
 * */

package wps.servicebus.control;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;

import java.util.Date;
import java.text.SimpleDateFormat;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.http.*;
import org.apache.log4j.Logger;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.handler.DefaultHandler;
import org.mortbay.jetty.handler.HandlerCollection;
import org.mortbay.jetty.servlet.Context;

import wps.servicebus.Config;
import wps.servicebus.ServHandler;
import wps.servicebus.agent.AgentApp;

/*class HandleRoot extends ServHandler
{	
	@Override
	public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
		throws IOException, ServletException
	{
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	    String tm = df.format(new Date()); 	
		
		OutputHtml(response, "Timestamp: " + tm);
		((Request)request).setHandled(true);
	}
}

class HandleCreate extends ServHandler
{
	private Scheduler m_scheduler;
	
	public HandleCreate(Scheduler scheduler)
	{
		m_scheduler = scheduler;
	}
	
	@Override
	public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
		throws IOException, ServletException
	{
		String user = request.getParameter("user");
		String token = request.getParameter("token");
		String file = request.getParameter("file");		
		Logger.println("user: " + user + ", token: " + token + ", file: " + file);
		
		Scheduler.CreateResult r = m_scheduler.CreateNew(token, user, file);
		ConnectionInfo con = r.Con;
		String rs;
		if (r.Status == 0)
		{
			rs = String.format("addr=%1s&port=%2d&key=%3s", 
				con.get_AppAddress(), con.get_AppPort(), con.get_Key());
		}
		else
			rs = "status=" + Integer.toString(r.Status);
	
		OutputHtml(response, rs);
		((Request)request).setHandled(true);
	}
}*/

/*
 * after the controller get start connection request from the client,
 * controller use handle the request by load balancing and pick one agent
 * to handle the request from the client. The request to start application 
 * is further sent to the agent and allow agent to handle the rest of the
 * work.
 * signature from client: 
 * <user> -- user
 * <token> -- token
 * <file> -- file
 * <key> -- key
 * 
 * the controller relay the client's request through httpClient and use 
 * wait for the response. After the agent start the application and then
 * send back the socket information of the application, this handler relay 
 * this socket information to the client and then use this information to edit
 * the appserver table and connection table. After these two tables are
 * edited to preserve the current status.
 * signature:
 * the socket information should looks like
 * -addr $addr -port $port
 * 
 * */
class HandleRequest extends ServHandler{
//	private Scheduler m_scheduler;
	private LoadBalancer m_loadBalancer;
	
	public HandleRequest(LoadBalancer loadBalancer){
//		m_scheduler = scheduler;
		m_loadBalancer = loadBalancer;
	}
	
	@Override
	public void handle(String target, HttpServletRequest request,
			HttpServletResponse response, int dispatch) throws IOException,
			ServletException {
		System.out.println("controller receive request from client...");
		
		String user = request.getParameter("user");
		String token = request.getParameter("token");
		String file = request.getParameter("file");
		String key = "key";
//		Logger.println("user: " + user + ", token: " + token + ", file: " + file);
				
//		Scheduler.CreateResult r = m_scheduler.CreateNew(token, user, file);
		
		//use load balancer to select an agent
//		AgentWorker agent = m_loadBalancer.agentBalancerPreRequest();
		AgentWorker agent = m_loadBalancer.agentBalancerPreServerInfo();
		
		//agent return the application server addr, port, process ID, connection number
		HttpClient httpclient = new DefaultHttpClient();
		HttpGet httpget = new HttpGet(agent.getURL() + "start/?user="+user+"&token="+token+"&file="+file+"&key="+key+"");
		
		//TODO:the timeout execption should be handled here
		HttpResponse res = httpclient.execute(httpget);
		HttpEntity entity = res.getEntity();			
		String message = EntityUtils.toString(entity);
		
		//edit the connection table and set this connection as not active
		Options opt = new Options();        
        opt.addOption("addr", true, "address");
        opt.addOption("port", true, "port");                
        String[] arg = message.split(" ");
        BasicParser parser = new BasicParser();
        CommandLine cl = null;
		try {
			cl = parser.parse(opt, arg);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}        
		ConnectionInfo connection = new ConnectionInfo(cl.getOptionValue("addr")
				, Integer.parseInt(cl.getOptionValue("port")));
		m_loadBalancer.addConnection(connection);
//		System.out.print("message from appserver: " + message);			
		
		//return app server connection info to the client	
		OutputHtml(response, message);
		m_loadBalancer.printAppServerTable();
		m_loadBalancer.printConnectionTable();
		((Request)request).setHandled(true);
	}	
}

/*
 * this handle always listen to the app server for connection start or destroy message
 * after the application successfully connect to the agent, the application
 * first send a message to the agent and then the agent will contact the controller
 * through this handler.
 * Monitor will use this information to change the connection and application table to
 * the right status. This information is not relayed further 
 * signature:
 * command = start : the connection between client and application successfully setup
 * addr = $addr : the addr of the application
 * port = $port : the port of the application
 * processID = $processID : the processID of the application
 * 
 * OR
 * command = destroy : the connection between client and application breaks up
 * addr = $addr : the addr of the application
 * agentPort = $agentPort : the port of the agent
 * appPort = $appPort : the port of the application * 
 * processID = $processID : the processID of the application
 * */
class HandleMonitor extends ServHandler{
	private LoadBalancer m_loadBalancer;
	
	public HandleMonitor(LoadBalancer loadBalancer){
		m_loadBalancer = loadBalancer;
	}
	
	@Override
	
	public void handle(String target, HttpServletRequest request,
			HttpServletResponse response, int dispatch) throws IOException,
			ServletException {		
		String command = request.getParameter("command");
		String addr = request.getParameter("addr");
		int agentPort = Integer.parseInt(request.getParameter("agentPort"));
		int appPort = Integer.parseInt(request.getParameter("appPort"));
		int processID = Integer.parseInt(request.getParameter("processID"));
		System.out.println("controller receive monitor information from agent...");		
//		System.out.format("parameter: %s %s %d %d %d\n", command, addr, agentPort, appPort, processID);		
		
		if(command.equals("start")){
//			System.out.println("controller start Connection...");
			m_loadBalancer.startConnection(addr, agentPort, appPort, processID);			
		}else if (command.equals("destroy")){
			System.out.println("controller destroy connection...");
			m_loadBalancer.destroyConnection(addr, agentPort, appPort, processID);
		}
		
		m_loadBalancer.printAppServerTable();
		m_loadBalancer.printConnectionTable();
		((Request)request).setHandled(true);
	}	
}

public class ControlApp
{
	static Logger logger = Logger.getLogger(ControlApp.class); 
	private Server m_server = null;
//	private Scheduler m_scheduler = null;
	private LoadBalancer m_loadBalancer = null;
	
	private void MapUrl(ContextHandlerCollection contexts, String path, AbstractHandler handler)
	{
		Context ctx = new Context();
		ctx.setContextPath(path);
		ctx.setHandler(handler);
		contexts.addHandler(ctx);
	}
	
	private void MapUrls()
	{
		ContextHandlerCollection contexts = new ContextHandlerCollection();
//		MapUrl(contexts, "/", new HandleRoot());
//		MapUrl(contexts, "/create", new HandleCreate(m_scheduler));
		MapUrl(contexts, "/start", new HandleRequest(m_loadBalancer));
		MapUrl(contexts, "/monitor", new HandleMonitor(m_loadBalancer));
		
		HandlerCollection handlers = new HandlerCollection();
		handlers.setHandlers(new Handler[]{contexts,new DefaultHandler()});
		m_server.setHandler(handlers);
	}
	
	public void Init(String ipHost, int portPublic, LoadBalancer loadBalancer)
	{
		try
		{
			
//			m_scheduler = new Scheduler();
			
			m_loadBalancer = loadBalancer;
			
			m_server = new Server(portPublic);
			MapUrls();
			m_server.start();			
			System.out.println("Starting host on " + portPublic + "...");
			register(m_loadBalancer.getAgents(), ipHost, portPublic);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	private void register(ArrayList<AgentWorker> agents, String addr, int port) throws ClientProtocolException, IOException{
		HttpClient httpclient = new DefaultHttpClient();		
		for(AgentWorker agent : agents){			
			HttpGet httpget = new HttpGet(agent.getURL() + "register/?addr="+addr+"&port="+port);			
			HttpResponse res = httpclient.execute(httpget);
			httpget.abort();
			//TODO: if the agent is not reachable then delete the agent from the list
		}
	}
	
	public static void main(String[] args)
	{		
/*		
		//start agent 1 and 2
		int port1 = 9000;
		AgentApp inst1 = new AgentApp();
		inst1.Init(port1);
		
		int port2 = 9001;
		AgentApp inst2 = new AgentApp();
		inst2.Init(port2);
		
		//init load balancer
//		ArrayList<AgentWorker> agents = new ArrayList<AgentWorker>();		
//		agents.add(new AgentWorker(9000, 50, 0));
//		agents.add(new AgentWorker(9001, 50, 0));
//		LoadBalancer lb = new LoadBalancer(agents);
		
		ArrayList<AppServerInfo> appServers = new ArrayList<AppServerInfo>();
		appServers.add(new AppServerInfo("127.0.0.1", port1, 0));
		appServers.add(new AppServerInfo("127.0.0.1", port2, 0));*/
		
		//use the config file to load the agent and other configuration		
		LoadBalancer lb = null;
		try {
			lb = new LoadBalancer(new Config(), true, true);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//start controller
		String ipHost = "127.0.0.1";
		int portPublic = 8000;
		
		ControlApp instance = new ControlApp();
		instance.Init(ipHost, portPublic, lb);
	}
}
